package grpcurl

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"gitee.com/shuokeyun/kratos/errors"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"io"
	"strconv"
	"strings"
	// Register gzip compressor so compressed responses will work
	_ "google.golang.org/grpc/encoding/gzip"
	// Register xds so xds and xds-experimental resolver schemes work
	_ "google.golang.org/grpc/xds"

	"github.com/fullstorydev/grpcurl"
)

var (
	protoFiles  multiString
	addlHeaders multiString
	rpcHeaders  multiString

	reflection = optionalBoolFlag{val: true}
)

type multiString []string

func (s *multiString) String() string {
	return strings.Join(*s, ",")
}

func (s *multiString) Set(value string) error {
	*s = append(*s, value)
	return nil
}

func CallGrpc(conn *grpc.ClientConn, bff, service, method, version string, str string) (interface{}, error) {
	symbol := fmt.Sprintf("shuokeyun.%s_bff.api.%s.%s/%s", bff, version, service, method)

	var buff bytes.Buffer
	buff.WriteString(str)
	var input rpcInput
	if err := json.Unmarshal(buff.Bytes(), &input); err != nil {
		panic(err)
	}
	return call(conn, symbol,
		[]string{"/Users/coco/code/gocode/geekbang-graduation/api-gateway/api/v1/med_bff.protoset"},
		input)
}

func call(cc *grpc.ClientConn, symbol string, protoset []string, input rpcInput) (interface{}, error) {
	if !reflection.set && (len(protoset) > 0 || len(protoFiles) > 0) {
		reflection.val = false
	}
	ctx := context.Background()

	var descSource grpcurl.DescriptorSource

	var fileSource grpcurl.DescriptorSource

	var err error
	fileSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...)
	if err != nil {
		return nil, errors.NewWrap(err, 500, "INTERNAL", "Failed to process proto descriptor sets.")
	}

	descSource = fileSource

	reqStats := rpcRequestStats{
		Total: len(input.Data),
	}
	requestFunc := func(m proto.Message) error {
		if len(input.Data) == 0 {
			return io.EOF
		}
		reqStats.Sent++
		req := input.Data[0]
		input.Data = input.Data[1:]
		if err := jsonpb.Unmarshal(bytes.NewReader([]byte(req)), m); err != nil {
			return status.Errorf(codes.InvalidArgument, err.Error())
		}
		return nil
	}
	h := rpcResult{
		descSource: descSource,
		Requests:   &reqStats,
	}

	err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), &h, requestFunc)
	if err != nil {
		if errStatus, ok := status.FromError(err); ok {
			fmt.Println(errStatus)
		} else {
			return nil, errors.NewWrap(err, 500, "INTERNAL", fmt.Sprintf("Error invoking method %q", symbol))
		}
	}
	if h.Error != nil {
		return nil, errors.New(int(h.Error.Code), h.Error.Name, h.Error.Message)
	}
	return h.Responses, nil
}

type optionalBoolFlag struct {
	set, val bool
}

func (f *optionalBoolFlag) String() string {
	if !f.set {
		return "unset"
	}
	return strconv.FormatBool(f.val)
}

func (f *optionalBoolFlag) Set(s string) error {
	v, err := strconv.ParseBool(s)
	if err != nil {
		return err
	}
	f.set = true
	f.val = v
	return nil
}

func (f *optionalBoolFlag) IsBoolFlag() bool {
	return true
}
